// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: Implementation of CSSEffects.
//   $Id$

#include "stdafx.h"
#include "dispex.h"
#include "ExecuteDispatch.h"
#include "CSSEffects.h"

const long g_lExtraLengthForTranslatingCSSToJScript = 26/*new Scriptaculous*/ + 7 /*target,*/ + 1 /*;*/ + 2/*{}*/ - 2 /*fx*/ + 15 /*try{}catch(e){}*/;

//WARNING!:  At present, this code is no better than regular expressions, as that is all that is desired
//	right now.  However, there will come a point at which the traditional regexp greedy/non-greedy
//	distinction will be insufficient, and brace-counting will be required.
//	If, at some point in the future, it is decided that one of the arguments can be (e.g.) a function,
//		we will have need of facilities unavailable in RegExp.
//		If we use regexp now, this whole thing will need to be rewritten.

HRESULT CSSEffects::CopyObjectMethodChain( BSTR & bsScriptCursor, BSTR &pCursor)
{
	HRESULT hr = S_OK;
	//Copy in the function name, translating '-' into '.'
	wchar_t * sSeparators = L"-(";
	wchar_t * pOpenParenthesis = wcschr(pCursor,L'(');
	wchar_t * pNextTranslatedToken = wcstok(pCursor,sSeparators);
	
	while(pNextTranslatedToken && (!pOpenParenthesis || pNextTranslatedToken < pOpenParenthesis))
	{
		bsScriptCursor[0] = L'.';
		++bsScriptCursor;
		wcscpy(bsScriptCursor,pNextTranslatedToken);
		bsScriptCursor += wcslen(pNextTranslatedToken);
		pNextTranslatedToken = wcstok(0,sSeparators);
	}
	pCursor = pNextTranslatedToken;
	bsScriptCursor[0] = L'(';
	++bsScriptCursor;
	return hr;
}

#define DEBUG_CHECK_CSS_TRANSITION_EFFECTS 1
#undef DEBUG_CHECK_CSS_TRANSITION_EFFECTS
HRESULT  CSSEffects::TranslateCSSCodeToJScript( BSTR bsCSSCode,BSTR * pbsScript)
{
	HRESULT hr = S_OK;
	//Sample code:
	//
	//	fx-Effect-Highlight(startcolor:'#4682b4' endcolor:'#00ffff' restorecolor:'#ffffff')
	//	should become:
	//	Scriptaculous.Effect.Highlight(target,{startcolor:'#4682b4',endcolor:'#00ffff',restorecolor:'#ffffff'});
	//
	//find "fx-" to make sure that this is the right kind of code to translate
	wchar_t * pCursor = wcsstr(bsCSSCode,L"fx-");
	if(pCursor)
	{
		pCursor += 2; //Shift to '-';
		
		//Get the length of the original, and the new strings
		long lOriginalLength = wcslen(bsCSSCode);
		long lNewLength = lOriginalLength + g_lExtraLengthForTranslatingCSSToJScript;
#ifdef DEBUG_CHECK_CSS_TRANSITION_EFFECTS
		lNewLength += 22;
#endif
		//Create a new BSTR with sufficient magnitude to take the translated script.
		CComBSTR bsScript(lNewLength);

		//Create the first part of the script:
		BSTR bsScriptCursor = bsScript.m_str;
		wcscpy(bsScriptCursor,L"try{new document.Scriptaculous.");
		bsScriptCursor += 30; //shunt the cursor to the end.
		//Copy from after cursor '-', up to the next translateable token, into the script.
		CopyObjectMethodChain(bsScriptCursor,pCursor);
		//Add the 'target' argument.
		wcscpy(bsScriptCursor,L"target");
		bsScriptCursor += 6;
		if(pCursor)
		{
			//Copy in the following argument if present, within {}
			//	translating ' ' for ','
			//Fetch the closing parenthesis.
			
			//WARNING!: This section is no better than a Regexp! 
			//	TODO: implement  string ignoring(probably use regexp) and brace counting for functions.
			wchar_t * sSeparators = L" \t)";
			wchar_t * pNextTranslatedToken = wcstok(pCursor,sSeparators);
			if(pNextTranslatedToken)
			{
				wcscpy(bsScriptCursor,L",{");
				bsScriptCursor += 2;
				while(pNextTranslatedToken)
				{
					wcscpy(bsScriptCursor,pNextTranslatedToken);
					bsScriptCursor += wcslen(pNextTranslatedToken);
					pCursor = pNextTranslatedToken;
					pNextTranslatedToken = wcstok(0,sSeparators);
					if(pNextTranslatedToken)
					{
						bsScriptCursor[0] = ',';
						++bsScriptCursor;
					}
				}
				bsScriptCursor[0] = L'}';
				++bsScriptCursor;
			}
		}
#ifdef DEBUG_CHECK_CSS_TRANSITION_EFFECTS
		wcscpy(bsScriptCursor,L");}catch(e){alert(e+e.description)}");
#else
		wcscpy(bsScriptCursor,L");}catch(e){}");
#endif
		*pbsScript = bsScript.Detach();
	}
	else
	{
		//If this were not just being used locally, this should copy
		//	It should probably copy anyway, to prevent double deallocs.
		*pbsScript = bsCSSCode;
		hr = S_FALSE;
	}
	return hr;
}


HRESULT  CSSEffects::GetEffectCSSCode(const CComBSTR & sEventName,IHTMLElement * pEl,CComBSTR & sCSSCode)
{

	HRESULT hr = E_INVALIDARG;
	CComQIPtr<IHTMLElement2> pEl2(pEl);
	if(pEl2)
	{
		CComPtr<IHTMLCurrentStyle> pStyle;
		hr = pEl2->get_currentStyle(&pStyle);
		//Custom properties are exposed through IDispatch
		CComQIPtr<IDispatch> pStyleAsDispatch(pStyle);
		if(pStyleAsDispatch)
		{
			//fixup the name of the event into the CSSCode format.
			CComBSTR obsName = L"-event-";
			obsName.Append(sEventName);
			BSTR bsName = obsName.m_str;
			//TODO: investigate whether DISPID may be cached here - I suspect not, as the style object is in constant flux
			DISPID d = 0;
			DISPPARAMS params = {0,0,0,0};
			CComVariant vtResult;
			hr = InvokeDispatch(pStyleAsDispatch,bsName,&params,&vtResult,d,DISPATCH_PROPERTYGET,LOCALE_SYSTEM_DEFAULT,0,0);
			//IE7 requires the preceding "-", IE6 ignores it.
			if(FAILED(hr))
			{
				hr = InvokeDispatch(pStyleAsDispatch,++bsName,&params,&vtResult,d,DISPATCH_PROPERTYGET,LOCALE_SYSTEM_DEFAULT,0,0);
			}

			if(SUCCEEDED(hr) && vtResult.vt == VT_BSTR)
			{
				vtResult.CopyTo(&sCSSCode);
			}
		}
	}
	return hr;
}

HRESULT  CSSEffects::ExecuteCSSCode(const CComBSTR & sCSSCode,IHTMLElement * pEl)
{
	CComBSTR bsScript;
	HRESULT hr = TranslateCSSCodeToJScript(sCSSCode,&bsScript);
	if(SUCCEEDED(hr))
	{
		hr = ExecuteJScriptOnTarget(bsScript,pEl);
	}
	return hr;
}

HRESULT CSSEffects::SetDispatchExProperty(IDispatchEx * pDispEx,BSTR sPropertyName, DISPID &didPropertyId, VARIANT * pvtPropertyValue)
{
	HRESULT hr = E_INVALIDARG;
	//If no dispid was provided, look it up.
	if(!didPropertyId && sPropertyName)
		hr = pDispEx->GetDispID(sPropertyName, fdexNameCaseInsensitive | fdexNameEnsure, &didPropertyId);
	
	if(didPropertyId)
	{
		//Create the dispparams structure.
		DISPID putid = DISPID_PROPERTYPUT;
		DISPPARAMS dparams;
		dparams.rgvarg = pvtPropertyValue;
		dparams.rgdispidNamedArgs = &putid;
		dparams.cArgs = 1;
		dparams.cNamedArgs = 1;
		//set the property
		hr = pDispEx->InvokeEx(didPropertyId, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dparams, 0, 0, 0);
	}

	return hr;

}

HRESULT  CSSEffects::ExecuteJScriptOnTarget(const CComBSTR & bsScript,IHTMLElement * pEl)
{
	//Invoke the script created by the bstr
	CComPtr<IDispatch> pDocDisp;
	HRESULT hr = pEl->get_document(&pDocDisp);
	CComQIPtr<IHTMLDocument2> pDoc2(pDocDisp);
	if(pDoc2)
	{
		CComPtr<IHTMLWindow2> pWin2;
		hr = pDoc2->get_parentWindow(&pWin2);	
		if(pWin2)
		{
			CComQIPtr<IDispatchEx> pDispEx(pWin2);
			DISPID id = 0;
			if(pDispEx)
			{
				CComBSTR sTargetPropertyName(L"target");
				CComVariant vDisp(pEl);
				hr =  SetDispatchExProperty(pDispEx,sTargetPropertyName, id,&vDisp);
			}
			
			MSG msg;
			// force msg-q to be created just in case, NOP otherwise
			PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE | PM_NOYIELD);
			// msg to myself to do work
			DWORD dwThread = GetCurrentThreadId();
			PostThreadMessage(dwThread, WM_USER+3, 0, 0);
			// msg-pump
			// while (GetMessage(&msg, NULL, 0, 0))
			while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) 
			{
				// this was my message -- time to do my work
				if (msg.hwnd == NULL && msg.message == WM_USER+3)
				{
					CComVariant vtRetVal;
					hr = pWin2->execScript(bsScript,L"JScript",&vtRetVal);
				}
				else
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
			
			//set the target property to NULL, to Release the element, and prevent incorrect
			//	references
			if(pDispEx && id)
			{
				CComVariant vNull = 0;
				hr =  SetDispatchExProperty(pDispEx,0, id,&vNull);
			}

		}
	}
	return hr;
}

HRESULT  CSSEffects::ShowEffect(const CComBSTR & sEventName,IHTMLElement * pEl)
{
	HRESULT hr = E_INVALIDARG;

	CComBSTR sCSSCode;
	hr = GetEffectCSSCode(sEventName,pEl,sCSSCode);
	if(SUCCEEDED(hr) && sCSSCode)
		hr = ExecuteCSSCode(sCSSCode,pEl);
	else
		hr = S_FALSE;

	return hr;
}
